package sf.querydsl;

import com.querydsl.core.JoinExpression;
import com.querydsl.sql.AbstractSQLQuery;
import com.querydsl.sql.Configuration;
import com.querydsl.sql.ProjectableSQLQuery;
import com.querydsl.sql.QueryDSLVistor;
import com.querydsl.sql.RelationalPath;
import com.querydsl.sql.SQLSerializer;
import com.querydsl.sql.dml.AbstractSQLClause;
import com.querydsl.sql.dml.AbstractSQLDeleteClause;
import com.querydsl.sql.dml.AbstractSQLInsertClause;
import com.querydsl.sql.dml.AbstractSQLUpdateClause;
import com.querydsl.sql.dml.QueryDSLDMLVistor;
import com.querydsl.sql.dml.SQLMergeClause;
import com.querydsl.sql.types.EnumAsObjectType;
import com.querydsl.sql.types.EnumByNameType;
import com.querydsl.sql.types.EnumByOrdinalType;
import com.querydsl.sql.types.Null;
import com.querydsl.sql.types.Type;
import sf.core.DBField;
import sf.core.DBObject;
import sf.database.jdbc.extension.ObjectJsonMapping;
import sf.database.meta.ColumnMapping;
import sf.database.meta.MetaHolder;
import sf.database.meta.TableMapping;
import sf.spring.util.CollectionUtils;

import javax.persistence.EnumType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public class QueryDSL {
    /**
     * 获取查询总数的接口
     * @param query
     * @return
     */
    public static String getCountSql(ProjectableSQLQuery query) {
        SQLSerializer serializer = serialize(query, true);
        String countSql = serializer.toString();
        return countSql;
    }

    /**
     * ProjectableSQLQuery中的protected SQLSerializer serialize(boolean forCountRow)方法
     * 执行sql的serialize方法
     * @param query
     * @param forCountRow
     * @return
     */
    public static SQLSerializer serialize(ProjectableSQLQuery query, boolean forCountRow) {
        return QueryDSLVistor.serialize(query, forCountRow);
    }

    /**
     * 获取内部的SQLRelationalPathBase
     * @param query
     * @return
     */
    public static List<Class<?>> getJoins(AbstractSQLQuery query) {
        List<JoinExpression> exprs = query.getMetadata().getJoins();
        List<Class<?>> list = Collections.emptyList();
        if (CollectionUtils.isNotEmpty(exprs)) {
            list = new ArrayList<>(exprs.size());
            for (JoinExpression je : exprs) {
                Class<?> clz = je.getTarget().getType();
                list.add(clz);
            }
        }
        return list;
    }

    /**
     * 获取表class
     * @param query
     * @return
     */
    public static Class<?> getQueryDSLTableClass(AbstractSQLQuery query) {
        List<Class<?>> joinTargets = QueryDSL.getJoins(query);
        Class<?> clz = null;
        for (Class<?> c : joinTargets) {
            if (DBObject.class.isAssignableFrom(c)) {
                clz = c;
                break;
            }
        }
        return clz;
    }

    public static Class<?> getInsertTableClass(AbstractSQLInsertClause intsert) {
        Class<?> clz = null;
        RelationalPath c = QueryDSLDMLVistor.getRelationalPath(intsert);
        if (c != null && c instanceof SQLRelationalPath) {
            clz = ((SQLRelationalPath) c).getClz();
        }
        return clz;
    }

    public static Class<?> getUpdateTableClass(AbstractSQLUpdateClause update) {
        Class<?> clz = null;
        RelationalPath c = QueryDSLDMLVistor.getRelationalPath(update);
        if (c != null && c instanceof SQLRelationalPath) {
            clz = ((SQLRelationalPath) c).getClz();
        }
        return clz;
    }

    public static Class<?> getDeleteTableClass(AbstractSQLDeleteClause delete) {
        Class<?> clz = null;
        RelationalPath c = QueryDSLDMLVistor.getRelationalPath(delete);
        if (c != null && c instanceof SQLRelationalPath) {
            clz = ((SQLRelationalPath) c).getClz();
        }
        return clz;
    }

    public static Class<?> getMergeTableClass(SQLMergeClause merge) {
        Class<?> clz = null;
        RelationalPath c = QueryDSLDMLVistor.getRelationalPath(merge);
        if (c != null && c instanceof SQLRelationalPath) {
            clz = ((SQLRelationalPath) c).getClz();
        }
        return clz;
    }


    /**
     * 设置返回自定义类型
     * @param clz
     * @param query
     * @param <T>
     */
    public static <T> void registerType(Class<T> clz, AbstractSQLQuery query) {
        Configuration c = getConfigurationQuery(query);
        registerType(clz, c);
    }

    /**
     * 获取返回类型
     * @param clz
     * @param c
     * @param <T>
     */
    public static <T> void registerType(Class<T> clz, Configuration c) {
        TableMapping tm = MetaHolder.getMeta(clz);
        for (Map.Entry<DBField, ColumnMapping> entry : tm.getSchemaMap().entrySet()) {
            ColumnMapping cm = entry.getValue();
            //自定义json处理注入
            if (cm.getType() != null && cm.getType().value() == ObjectJsonMapping.class) {
                Class<?> javaType = c.getJavaType(0, null, 0, 0, tm.getTableName(), cm.getRawColumnName());
                if (javaType == Null.class || javaType == null) {
                    QueryDSLObjectMapJsonType<Object> queryDSLObjectMapJsonType = new QueryDSLObjectMapJsonType<>();
                    queryDSLObjectMapJsonType.setReturnedClass(cm.getHandler().getDefaultJavaType());
                    queryDSLObjectMapJsonType.setGenericType(cm.getFieldAccessor().getGenericType());
                    c.register(tm.getTableName(), cm.getRawColumnName(), queryDSLObjectMapJsonType);
                }
            }
            if (cm.getClz().isEnum()) {
                EnumType et = EnumType.STRING;
                if (cm.getEnumerated() != null) {
                    et = cm.getEnumerated().value();
                }
                switch (et) {
                    case STRING:
                        c.register(tm.getTableName(), cm.getRawColumnName(), new EnumByNameType(cm.getClz()));
                        break;
                    case ORDINAL:
                        c.register(tm.getTableName(), cm.getRawColumnName(), new EnumByOrdinalType(cm.getClz()));
                        break;
                    default:
                        c.register(tm.getTableName(), cm.getRawColumnName(), new EnumAsObjectType(cm.getClz()));
                        break;
                }
            }
        }
    }

    public static <T> void registerType(ColumnMapping cm, Type type, Configuration c) {
        c.register(cm.getMeta().getTableName(), cm.getRawColumnName(), type);
    }

    public static Configuration getConfigurationQuery(AbstractSQLQuery query) {
        Configuration c = QueryDSLVistor.getConfiguration(query);
        return c;
    }

    public static Configuration getConfigurationDML(AbstractSQLClause query) {
        Configuration c = QueryDSLDMLVistor.getConfiguration(query);
        return c;
    }

}
